package org.myspringframework.aop.framework.autoproxy;

import org.aopalliance.aop.Advice;
import org.aspectj.lang.annotation.Aspect;
import org.myspringframework.aop.*;
import org.myspringframework.aop.framework.ProxyFactory;
import org.myspringframework.beans.BeansException;
import org.myspringframework.beans.factory.BeanFactory;
import org.myspringframework.beans.factory.aware.BeanFactoryAware;
import org.myspringframework.beans.factory.config.beandefinition.PropertyValues;
import org.myspringframework.beans.factory.config.postprocessor.SmartInstantiationAwareBeanPostProcessor;
import org.myspringframework.beans.factory.support.DefaultListableBeanFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

//基于AspectJ的代理生成器
//todo:对advisor排序
public class AspectJAwareAdvisorAutoProxyCreator implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    private DefaultListableBeanFactory beanFactory;
    //保存容器中的所有advisor切面
    private List<Advisor> advisors;
    //保存提前生成aop代理的bean，如果提前生成代理了，那么后续不会继续创建代理 key:beanName,value:bean
    protected ConcurrentHashMap<String,Object> earlyProxyReferences=new ConcurrentHashMap<>();

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean!=null){
            //如果出现循环依赖提前创建过代理，则无需重复创建
            if (earlyProxyReferences.remove(beanName)!=bean){
                return wrapIfNecessary(bean,beanName);
            }
        }
        return bean;
    }

    //如果需要创建代理，则创建代理
    protected Object wrapIfNecessary(Object bean, String beanName){
        //如果是aop的基础设施类或者advisor,则不进行代理
        if (isInfrastructureClass(bean.getClass())){
            return bean;
        }else {
            //获取容器中所有的advisor
            findCandidateAdvisors();
            if (advisors==null||advisors.size()==0){
                return bean;
            }

            //遍历advisors寻找匹配该类的advisor切面
            List<Advisor> eligibleAdvisors=new ArrayList<>();
            for (Advisor advisor : advisors) {
                if (advisor instanceof PointcutAdvisor){
                    PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
                    boolean matches = pointcutAdvisor.getPointcut().getClassFilter().matches(bean.getClass());
                    if (matches){
                        eligibleAdvisors.add(advisor);
                    }
                }
            }

            if (eligibleAdvisors.size()!=0){
                //有符合条件的advisor切面
                AdvisedSupport advisedSupport = new AdvisedSupport();
                advisedSupport.setAdvisors(eligibleAdvisors);
                TargetSource targetSource = new TargetSource(bean);
                advisedSupport.setTargetSource(targetSource);


                ProxyFactory proxyFactory = new ProxyFactory(advisedSupport);
                Object proxy = proxyFactory.getProxy();
                return proxy;
            }
        }
        return bean;
    }

    //判断是否为判断给定的类是否是 Spring AOP 框架自身的类（基础设施类），或者是与 AOP 运行时相关的类。
    private boolean isInfrastructureClass(Class<?> beanClass) {
        return Advice.class.isAssignableFrom(beanClass) || Pointcut.class.isAssignableFrom(beanClass) || Advisor.class.isAssignableFrom(beanClass) || beanClass.isAnnotationPresent(Aspect.class);
    }

    //获取容器中所有的advisor切面
    protected List<Advisor> findCandidateAdvisors(){
        if (advisors==null){
            Map<String, Advisor> advisors = beanFactory.getBeansOfType(Advisor.class);
            List<Advisor> collect = advisors.values().stream().collect(Collectors.toList());
            this.advisors=new ArrayList<>();
            this.advisors.addAll(collect);
        }
        return this.advisors;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        return null;
    }

    @Override
    public PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        return pvs;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory= (DefaultListableBeanFactory) beanFactory;
    }

    public DefaultListableBeanFactory getBeanFactory(){
        return this.beanFactory;
    }
}
